import sys
import tkinter as tk
from tkinter import ttk, scrolledtext, filedialog, messagebox
import sqlite3
import threading
import time
import os
import re
import warnings
from datetime import datetime
from PIL import Image, ImageDraw, ImageFont
import paddleocr
import numpy as np
from paddleocr import PaddleOCR

# 忽略一些常见的警告
warnings.filterwarnings("ignore", category=UserWarning, module="paddle.utils.cpp_extension.extension_utils")
warnings.filterwarnings("ignore", category=UserWarning, module="paddle")


class TestTool:
    """测试工具主类"""

    def __init__(self):
        self.root = tk.Tk()
        self.root.title("显卡测试工具")
        self.root.geometry("800x600")

        # 测试状态变量
        self.is_testing = False
        self.test_thread = None
        self.test_result_subfolder = None  # 测试结果子文件夹路径
        self.test_product_number = None  # 测试商品货号

        # 截图监控相关变量
        self.screenshot_files_before = set()  # 测试开始前的截图文件列表
        self.test_results = {}  # 测试结果字典
        self.duplicate_detection = {}  # 重复检测字典

        # OCR相关变量
        self.ocr_reader = None  # EasyOCR读取器

        # 报告相关全局变量
        self.furmark_report_text = None
        self.mark3d_report_text = None
        self.xianka_full_name_cached = None
        self.last_gpu_z_info = None
        self.furmark_core_temp_num = None
        self.furmark_hotspot_temp_num = None
        self.furmark_pass = None
        self.furmark_minutes = None
        self.furmark_seconds = None
        self.furmark_fps_num = None
        self.mark3d_pass_num = None
        self.mark3d_pass = None
        self.mark3d_pass_text = None
        self.latest_report_path = None

        # 数据库连接
        self.db_connection = None
        self.init_database()

        # 初始化OCR
        self.init_ocr()
        # 设置 OCR 路径
        os.environ["PPOCR_HOME"] = self.resource_path("paddleocr")
        # 初始化界面
        self.init_ui()

    def init_database(self):
        """初始化数据库"""
        try:
            self.db_connection = sqlite3.connect('test_results.db')
            cursor = self.db_connection.cursor()

            # 创建目录设置表
            cursor.execute('''
                CREATE TABLE IF NOT EXISTS directory_settings (
                    id INTEGER PRIMARY KEY AUTOINCREMENT,
                    setting_name TEXT UNIQUE,
                    setting_value TEXT,
                    created_at TEXT DEFAULT CURRENT_TIMESTAMP
                )
            ''')

            self.db_connection.commit()

            # 初始化目录设置
            self.init_directory_settings()

        except Exception as e:
            print(f"数据库初始化错误: {e}")


    # 解决打包后 OCR 依赖问题
    @staticmethod
    def resource_path(relative_path):
        """ 获取资源绝对路径 """
        try:
            base_path = sys._MEIPASS
        except Exception:
            base_path = os.path.abspath(".")
        return os.path.join(base_path, relative_path)

    def init_ocr(self):
        """初始化OCR读取器"""
        try:
            if self.ocr_reader is None:
                print("开始初始化PaddleOCR...")
                
                # 检查paddleocr模块是否可用
                try:
                    import paddleocr
                    print("✓ paddleocr模块导入成功")
                except ImportError as e:
                    error_msg = f"paddleocr模块导入失败: {e}"
                    print(f"❌ {error_msg}")
                    messagebox.showerror("OCR初始化失败", f"PaddleOCR模块导入失败:\n{error_msg}\n\n请检查PaddleOCR是否正确安装")
                    return
                
                # 检查PaddleOCR类是否可用
                try:
                    from paddleocr import PaddleOCR
                    print("✓ PaddleOCR类导入成功")
                except ImportError as e:
                    error_msg = f"PaddleOCR类导入失败: {e}"
                    print(f"❌ {error_msg}")
                    messagebox.showerror("OCR初始化失败", f"PaddleOCR类导入失败:\n{error_msg}\n\n请检查PaddleOCR版本是否正确")
                    return
                
                # 尝试创建OCR实例
                try:
                    print("正在创建PaddleOCR实例...")
                    self.ocr_reader = PaddleOCR(
                        use_doc_orientation_classify=False,  # 禁用文档方向分类模型
                        use_doc_unwarping=False,            # 禁用图像矫正模型
                        use_textline_orientation=False,     # 禁用文本行方向分类模型
                        lang="ch",                          # 中文模型
                        # 性能优化参数
                        text_rec_score_thresh=0.8,
                        text_det_thresh=0.3,                # 降低检测阈值
                        text_det_box_thresh=0.5,            # 降低检测框阈值
                        text_det_unclip_ratio=1.6,          # 调整检测框扩展比例
                    )
                    print("✓ PaddleOCR实例创建成功")
                    
                    # 测试OCR是否正常工作
                    try:
                        print("正在测试OCR功能...")
                        import numpy as np
                        # 创建一个简单的测试图像
                        test_image = np.ones((100, 300, 3), dtype=np.uint8) * 255
                        result = self.ocr_reader.predict(test_image)
                        print("✓ OCR功能测试通过")
                    except Exception as test_e:
                        print(f"⚠ OCR功能测试失败: {test_e}")
                        # 测试失败不影响使用，继续初始化
                except Exception as e:
                    error_msg = f"PaddleOCR实例创建失败: {e}"
                    print(f"❌ {error_msg}")
                    messagebox.showerror("OCR初始化失败", f"PaddleOCR实例创建失败:\n{error_msg}\n\n可能的原因:\n1. 模型文件缺失\n2. 内存不足\n3. 依赖库版本不兼容")
                    return
                
                print("✓ OCR读取器初始化成功")
                
        except Exception as e:
            error_msg = f"OCR初始化过程中发生未知错误: {e}"
            print(f"❌ {error_msg}")
            import traceback
            traceback.print_exc()
            messagebox.showerror("OCR初始化失败", f"OCR初始化过程中发生未知错误:\n{error_msg}\n\n详细错误信息已输出到控制台")
            self.ocr_reader = None

    @staticmethod
    def get_gpu_z_coordinates():
        """获取GPU-Z图片中需要识别的坐标区域"""
        # 这些坐标需要根据实际的GPU-Z截图进行调整
        # 格式：(x1, y1, x2, y2) - 左上角和右下角坐标
        # 坐标是基于图片的像素位置，需要根据实际GPU-Z界面调整

        coordinates = {
            'gpu_name': (132, 98, 431, 127),  # GPU名称 - 通常在左上角
            "xianka_gys": (131, 289, 275, 312),  # 显卡供应商
            "hexin_xinghao": (132, 136, 250, 163),  # 核心型号
            "cuda_hexin": (132, 364, 300, 388),  # cuda核心个数
            'xiancun_type': (132, 437, 318, 463),  # 显存类型
            'xaincun_size': (132, 475, 272, 499),  # 显存大小
            "memery_frequency": (309, 587, 400, 612),  # 显存频率
            'basic_core_frequency': (132, 587, 221, 613),  # 基础核心频率
            'boost_core_frequency': (471, 588, 558, 613),  # 超频频率
        }

        return coordinates

    @staticmethod
    def get_furmark_coordinates():
        """获取Furmark图片中需要识别的坐标区域"""
        # 这些坐标需要根据实际的Furmark截图进行调整
        # 格式：(x1, y1, x2, y2) - 左上角和右下角坐标

        coordinates = {
            'stress_test_time': (10, 235, 250, 260),  # 拷机时间
            'fps': (1, 156, 150, 185),  # FPS
            'frame_rate': (1, 190, 350, 240),  # 帧率
            'core_temperature': (200, 620, 480, 650),  # 核心温度
            'hotspot_temperature': (200, 653, 480, 677),  # 热点温度
        }

        return coordinates

    @staticmethod
    def get_3dmark_coordinates():
        """获取3DMark图片中需要识别的坐标区域"""
        # 这些坐标需要根据实际的3DMark截图进行调整
        # 格式：(x1, y1, x2, y2) - 左上角和右下角坐标

        coordinates = {
            'pass_rate': (130, 370, 370, 460),  # 通过率
        }

        return coordinates

    @staticmethod
    def crop_image_region(image, coordinates):
        """根据坐标裁剪图片区域"""
        try:
            x1, y1, x2, y2 = coordinates
            width, height = image.size

            # 验证坐标是否在图片范围内
            x1 = max(0, min(x1, width))
            y1 = max(0, min(y1, height))
            x2 = max(0, min(x2, width))
            y2 = max(0, min(y2, height))

            # 确保坐标有效
            if x1 >= x2 or y1 >= y2:
                print(f"无效的坐标: {coordinates}")
                return None

            cropped_image = image.crop((x1, y1, x2, y2))
            return cropped_image
        except Exception as e:
            print(f"裁剪图片区域错误: {e}")
            return None

    def recognize_text_from_image(self, image, filter_by_size=False):
        """使用PaddleOCR识别图片中的文本，并根据参数处理数字"""
        if self.ocr_reader is None:
            self.init_ocr()

        if self.ocr_reader is None:
            return "OCR未初始化"

        try:
            # 将PIL图像转换为numpy数组
            image_array = np.array(image)

            # 使用PaddleOCR的predict方法
            result = self.ocr_reader.predict(image_array)

            recognized_text = []

            # 提取rec_texts中的文本（静默，不打印调试信息）
            if result:
                for res in result:
                    if isinstance(res, dict) and 'rec_texts' in res:
                        rec_texts = res['rec_texts']
                        if isinstance(rec_texts, list):
                            for text in rec_texts:
                                if text and isinstance(text, str):
                                    recognized_text.append(text.strip())
                    elif hasattr(res, 'rec_texts'):
                        rec_texts = res.rec_texts
                        if isinstance(rec_texts, list):
                            for text in rec_texts:
                                if text and isinstance(text, str):
                                    recognized_text.append(text.strip())
            
            if filter_by_size:
                # 提取数字
                filtered_numbers = []
                for text in recognized_text:
                    import re
                    match = re.search(r'\b(\d+)\b', text)
                    if match:
                        num = int(match.group(1))
                        if num > 100 and num % 10 == 0:
                            filtered_numbers.append(str(num // 10))
                        else:
                            filtered_numbers.append(str(num))
                
                return ' '.join(filtered_numbers) if filtered_numbers else "未找到数字"
            
            return ' '.join(recognized_text) if recognized_text else "未识别到文本"

        except Exception as e:
            print(f"OCR识别错误: {e}")
            import traceback
            traceback.print_exc()
            return "OCR识别失败"

    def process_gpu_z_image(self, image_path):
        """处理GPU-Z图片，识别关键参数"""
        try:
            # 读取图片
            image = self.safe_imread(image_path)
            if image is None:
                return None

            # 获取固定坐标区域
            coordinates = self.get_gpu_z_coordinates()

            # 存储识别结果
            gpu_z_info = {}

            # 对每个区域进行OCR识别
            for region_name, coords in coordinates.items():
                # 裁剪区域
                cropped_image = self.crop_image_region(image, coords)
                if cropped_image:
                    # 保存裁剪的区域图片（用于调试）
                # 中间裁剪图不再保存

                    # 识别文本
                    recognized_text = self.recognize_text_from_image(cropped_image)
                    gpu_z_info[region_name] = recognized_text
                # 静默OCR结果日志，减少控制台输出
                else:
                    gpu_z_info[region_name] = "裁剪失败"

            return gpu_z_info

        except Exception as e:
            print(f"处理GPU-Z图片错误: {e}")
            return None

    def process_furmark_image(self, image_path):
        """处理Furmark图片，识别关键参数"""
        try:
            # 读取图片
            image = self.safe_imread(image_path)
            if image is None:
                return None

            # 获取固定坐标区域
            coordinates = self.get_furmark_coordinates()

            # 存储识别结果
            furmark_info = {}

            # 对每个区域进行OCR识别
            for region_name, coords in coordinates.items():
                # 裁剪区域
                cropped_image = self.crop_image_region(image, coords)
                if cropped_image:
                    # 保存裁剪的区域图片（用于调试）
                    # 中间裁剪图不再保存

                    # 识别文本，温度相关文本启用大小过滤
                    is_temperature = region_name in ['core_temperature', 'hotspot_temperature']
                    recognized_text = self.recognize_text_from_image(cropped_image, filter_by_size=is_temperature)
                    furmark_info[region_name] = recognized_text
                    # 静默OCR结果日志
                else:
                    furmark_info[region_name] = "裁剪失败"

            return furmark_info

        except Exception as e:
            print(f"处理Furmark图片错误: {e}")
            return None

    def process_3dmark_image(self, image_path):
        """处理3DMark图片，识别关键参数"""
        try:
            # 读取图片
            image = self.safe_imread(image_path)
            if image is None:
                return None

            # 获取固定坐标区域
            coordinates = self.get_3dmark_coordinates()

            # 存储识别结果
            mark3d_info = {}

            # 对每个区域进行OCR识别
            for region_name, coords in coordinates.items():
                # 裁剪区域
                cropped_image = self.crop_image_region(image, coords)
                if cropped_image:
                    # 保存裁剪的区域图片（用于调试）
                    # 中间裁剪图不再保存

                    # 识别文本
                    recognized_text = self.recognize_text_from_image(cropped_image)
                    mark3d_info[region_name] = recognized_text
                    # 静默OCR结果日志
                else:
                    mark3d_info[region_name] = "裁剪失败"

            return mark3d_info

        except Exception as e:
            print(f"处理3DMark图片错误: {e}")
            return None

    def init_directory_settings(self):
        """初始化目录设置"""
        try:
            cursor = self.db_connection.cursor()

            # 检查并初始化截图目录设置
            cursor.execute('SELECT setting_value FROM directory_settings WHERE setting_name = ?', ('screenshot_dir',))
            result = cursor.fetchone()
            if not result:
                cursor.execute('INSERT INTO directory_settings (setting_name, setting_value) VALUES (?, ?)',
                               ('screenshot_dir', None))

            # 检查并初始化测试结果目录设置
            cursor.execute('SELECT setting_value FROM directory_settings WHERE setting_name = ?', ('result_dir',))
            result = cursor.fetchone()
            if not result:
                cursor.execute('INSERT INTO directory_settings (setting_name, setting_value) VALUES (?, ?)',
                               ('result_dir', None))

            self.db_connection.commit()

            # 加载目录设置
            self.load_directory_settings()

        except Exception as e:
            print(f"目录设置初始化错误: {e}")

    def load_directory_settings(self):
        """从数据库加载目录设置"""
        try:
            cursor = self.db_connection.cursor()

            # 加载截图目录
            cursor.execute('SELECT setting_value FROM directory_settings WHERE setting_name = ?', ('screenshot_dir',))
            result = cursor.fetchone()
            self.screenshot_dir = result[0] if result and result[0] != 'None' else None

            # 加载测试结果目录
            cursor.execute('SELECT setting_value FROM directory_settings WHERE setting_name = ?', ('result_dir',))
            result = cursor.fetchone()
            self.result_dir = result[0] if result and result[0] != 'None' else None

        except Exception as e:
            print(f"加载目录设置错误: {e}")
            self.screenshot_dir = None
            self.result_dir = None

    def save_directory_setting(self, setting_name, directory_path):
        """保存目录设置到数据库"""
        try:
            cursor = self.db_connection.cursor()
            cursor.execute('''
                INSERT OR REPLACE INTO directory_settings (setting_name, setting_value) 
                VALUES (?, ?)
            ''', (setting_name, directory_path))
            self.db_connection.commit()

            # 更新内存中的设置
            if setting_name == 'screenshot_dir':
                self.screenshot_dir = directory_path
            elif setting_name == 'result_dir':
                self.result_dir = directory_path

        except Exception as e:
            print(f"保存目录设置错误: {e}")

    def select_directory(self, setting_name, title):
        """选择目录"""
        try:
            # 确定初始目录
            initial_dir = None
            if setting_name == 'screenshot_dir':
                initial_dir = self.screenshot_dir
            elif setting_name == 'result_dir':
                initial_dir = self.result_dir

            # 如果目录不存在或为None，使用桌面
            if not initial_dir or not os.path.exists(initial_dir):
                # Windows系统桌面路径
                if os.name == 'nt':  # Windows
                    initial_dir = os.path.join(os.path.expanduser("~"), "Desktop")
                else:  # Unix/Linux/Mac
                    initial_dir = os.path.expanduser("~/Desktop")

            # 打开目录选择器
            selected_dir = filedialog.askdirectory(
                title=title,
                initialdir=initial_dir
            )

            if selected_dir:
                # 保存到数据库
                self.save_directory_setting(setting_name, selected_dir)
                messagebox.showinfo("成功", f"目录设置已保存: {selected_dir}")

                # 更新显示
                self.update_directory_display()
            else:
                messagebox.showinfo("提示", "未选择目录，设置保持不变")

        except Exception as e:
            messagebox.showerror("错误", f"选择目录时发生错误: {e}")

    def update_directory_display(self):
        """更新目录显示"""
        try:
            # 更新截图目录显示
            screenshot_text = self.screenshot_dir if self.screenshot_dir else "未设置"
            self.screenshot_label.config(text=f"截图目录: {screenshot_text}")

            # 更新测试结果目录显示
            result_text = self.result_dir if self.result_dir else "未设置"
            self.result_label.config(text=f"测试结果目录: {result_text}")

        except Exception as e:
            print(f"更新目录显示错误: {e}")

    def open_screenshot_directory(self):
        """打开截图目录"""
        try:
            if self.screenshot_dir and os.path.exists(self.screenshot_dir):
                # 构建截图目录路径（截图目录 + 当前年月）
                current_date = datetime.now().strftime('%Y-%m')
                screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

                # 如果日期目录不存在，创建它
                if not os.path.exists(screenshot_date_dir):
                    os.makedirs(screenshot_date_dir, exist_ok=True)

                # Windows系统
                if os.name == 'nt':
                    os.startfile(screenshot_date_dir)
                # Unix/Linux/Mac系统
                else:
                    os.system(f'xdg-open "{screenshot_date_dir}"')
            else:
                messagebox.showwarning("警告", "截图目录未设置或不存在，请先设置截图目录")
        except Exception as e:
            messagebox.showerror("错误", f"打开截图目录失败: {e}")

    def open_result_directory(self):
        """打开测试结果目录"""
        try:
            if self.result_dir and os.path.exists(self.result_dir):
                # Windows系统
                if os.name == 'nt':
                    os.startfile(self.result_dir)
                # Unix/Linux/Mac系统
                else:
                    os.system(f'xdg-open "{self.result_dir}"')
            else:
                messagebox.showwarning("警告", "测试结果目录未设置或不存在，请先设置测试结果目录")
        except Exception as e:
            messagebox.showerror("错误", f"打开测试结果目录失败: {e}")

    @staticmethod
    def _find_report_in_dir(dir_path):
        """查找目录中的测试报告文件，返回路径或None"""
        try:
            if not os.path.isdir(dir_path):
                return None
            for f in os.listdir(dir_path):
                if f.endswith("测试报告.jpg"):
                    return os.path.join(dir_path, f)
            return None
        except Exception:
            return None

    def create_test_result_subfolder(self):
        """创建测试结果子文件夹"""
        try:
            # 检查测试结果目录是否设置
            if not self.result_dir:
                messagebox.showwarning("警告", "请先设置测试结果目录")
                return False

            # 检查测试结果目录是否存在
            if not os.path.exists(self.result_dir):
                messagebox.showwarning("警告", "测试结果目录不存在，请重新设置")
                return False

            # 获取结果目录下的子文件夹，找到最后（数字最大）的一个
            subfolders = []
            for item in os.listdir(self.result_dir):
                item_path = os.path.join(self.result_dir, item)
                if os.path.isdir(item_path):
                    subfolders.append(item)

            last_number = 0
            last_folder_name = None
            for folder in subfolders:
                m = re.match(r'^(\d+)', folder)
                if m:
                    num = int(m.group(1))
                    if num >= last_number:
                        last_number = num
                        last_folder_name = folder

            if last_folder_name:
                last_dir_path = os.path.join(self.result_dir, last_folder_name)
                report_path = self._find_report_in_dir(last_dir_path)
                if report_path is None:
                    # 上次未完成：清空图片，并将目录名重命名为"货号"（纯数字）
                    for file in os.listdir(last_dir_path):
                        if file.lower().endswith(('.jpg', '.png')):
                            try:
                                os.remove(os.path.join(last_dir_path, file))
                            except Exception:
                                pass
                    target_numeric_name = str(last_number)
                    target_path = os.path.join(self.result_dir, target_numeric_name)
                    if os.path.normpath(last_dir_path) != os.path.normpath(target_path):
                        try:
                            # 若目标已存在则清理其图片后使用它
                            if os.path.exists(target_path) and os.path.isdir(target_path) and target_path.lower() != last_dir_path.lower():
                                for f in os.listdir(target_path):
                                    if f.lower().endswith(('.jpg', '.png')):
                                        try:
                                            os.remove(os.path.join(target_path, f))
                                        except Exception:
                                            pass
                            os.rename(last_dir_path, target_path)
                            last_dir_path = target_path
                        except Exception:
                            # 无法重命名则继续沿用原目录
                            pass
                    self.test_product_number = str(last_number)
                    self.test_result_subfolder = os.path.normpath(last_dir_path)
                    return True

            # 上次已完成或没有历史：创建新的"货号"目录
            new_folder_number = last_number + 1
            self.test_product_number = str(new_folder_number)
            self.test_result_subfolder = os.path.normpath(os.path.join(self.result_dir, self.test_product_number))
            os.makedirs(self.test_result_subfolder, exist_ok=True)

            return True

        except Exception as e:
            messagebox.showerror("错误", f"创建测试结果子文件夹失败: {e}")
            return False

    def get_screenshot_files(self):
        """获取截图目录中的所有文件"""
        try:
            if not self.screenshot_dir or not os.path.exists(self.screenshot_dir):
                return set()

            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            # 如果日期目录不存在，返回空集合
            if not os.path.exists(screenshot_date_dir):
                return set()

            files = set()
            for item in os.listdir(screenshot_date_dir):
                item_path = os.path.join(screenshot_date_dir, item)
                if os.path.isfile(item_path):
                    files.add(item)
            return files
        except Exception as e:
            print(f"获取截图文件列表错误: {e}")
            return set()

    @staticmethod
    def identify_test_type(filename):
        """根据文件名识别测试类型"""
        filename_lower = filename.lower()

        if "3dmark" in filename_lower:
            return "3DMark"
        elif "furmark" in filename_lower:
            return "Furmark"
        elif "gpu-z" in filename_lower:
            return "GPU-Z"
        elif "computerz_cn" or "鲁大师" in filename_lower:
            return "鲁大师"
        else:
            return None

    def handle_new_screenshots(self, new_files):
        """处理新的截图文件"""
        try:
            # 按测试类型分组新文件
            test_groups = {}
            for file in new_files:
                test_type = self.identify_test_type(file)
                if test_type:
                    if test_type not in test_groups:
                        test_groups[test_type] = []
                    test_groups[test_type].append(file)

            # 处理每个测试类型
            for test_type, files in test_groups.items():
                if len(files) >= 2:
                    # 检测到重复文件，删除多余的文件
                    self.remove_duplicate_files(files)
                    # 只保留一个文件
                    remaining_file = files[0]
                    self.test_results[test_type] = remaining_file

                    # 如果是GPU-Z，进行OCR识别
                    if test_type == "GPU-Z":
                        self.process_gpu_z_ocr(remaining_file)
                    elif test_type == "Furmark":
                        self.process_furmark_ocr(remaining_file)
                    elif test_type == "3DMark":
                        self.process_3dmark_ocr(remaining_file)

                    self.root.after(0, self.update_result_text, f"✅ {test_type}测试完成\n")
                elif len(files) == 1:
                    # 检查是否与之前的文件冲突
                    if test_type in self.test_results:
                        # 删除之前的文件
                        old_file = self.test_results[test_type]
                        self.remove_old_file(old_file)
                        self.root.after(0, self.update_result_text, f"🔄 更新{test_type}测试结果\n")

                    # 记录新文件
                    self.test_results[test_type] = files[0]

                    # 如果是GPU-Z，进行OCR识别
                    if test_type == "GPU-Z":
                        self.process_gpu_z_ocr(files[0])
                        self.gpu_z_finished = True
                    elif test_type == "Furmark":
                        self.process_furmark_ocr(files[0])
                        self.furmark_finished = True
                    elif test_type == "3DMark":
                        self.process_3dmark_ocr(files[0])
                        self.mark3d_finished = True
                    else:
                        self.ludashi_finished = True
                    self.root.after(0, self.update_result_text, f"✅ {test_type}测试完成\n")

        except Exception as e:
            print(f"处理新截图文件错误: {e}")

    def process_gpu_z_ocr(self, filename):
        """处理GPU-Z图片的OCR识别"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            # 构建完整的文件路径
            image_path = os.path.join(screenshot_date_dir, filename)

            if not os.path.exists(image_path):
                print(f"GPU-Z图片不存在: {image_path}")
                return

            # 进行OCR识别
            gpu_z_info = self.process_gpu_z_image(image_path)

            if gpu_z_info:
                # 缓存用于最终报告
                self.last_gpu_z_info = dict(gpu_z_info)
                # 规范化 GPU 名称中的显存容量后缀（例如追加或修正为 "6GB/8GB/12GB"）
                try:
                    mem_size_gb = self.convert_memory_size_to_gb_integer(gpu_z_info.get('xaincun_size', ''))
                    if mem_size_gb and mem_size_gb != '未知':
                        gpu_z_info['gpu_name'] = self.ensure_gpu_name_suffix(gpu_z_info.get('gpu_name', ''), mem_size_gb)
                except Exception as _e:
                    print(f"规范化GPU名称时出错: {_e}")
                # 格式化识别结果
                formatted_info = self.format_gpu_z_info(gpu_z_info)

                # 生成特定格式的文本输出
                formatted_text, xianka_full_name = self.generate_gpu_z_formatted_text(gpu_z_info)
                self.xianka_full_name_cached = xianka_full_name

                # 在结果框中显示识别结果
                result_text = f"\n🔍 GPU-Z参数识别结果:\n"
                for param_name, value in formatted_info.items():
                    result_text += f"  {param_name}: {value}\n"

                # 添加显卡全名和格式化文本
                result_text += f"\n🎯 显卡全名: {xianka_full_name}\n"
                result_text += f"\n📝 格式化输出:\n{formatted_text}\n"

                self.root.after(0, self.update_result_text, result_text)

                # 保存识别结果到测试结果目录
                if self.test_result_subfolder:
                    self.save_gpu_z_info(formatted_info, formatted_text, xianka_full_name)
            else:
                self.root.after(0, self.update_result_text, "❌ GPU-Z参数识别失败\n")

        except Exception as e:
            print(f"处理GPU-Z OCR错误: {e}")
            self.root.after(0, self.update_result_text, f"❌ GPU-Z OCR处理错误: {e}\n")

    def process_furmark_ocr(self, filename):
        """处理Furmark图片的OCR识别"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            # 构建完整的文件路径
            image_path = os.path.join(screenshot_date_dir, filename)

            if not os.path.exists(image_path):
                print(f"Furmark图片不存在: {image_path}")
                return

            # 进行OCR识别
            furmark_info = self.process_furmark_image(image_path)

            if furmark_info:
                # 格式化识别结果
                formatted_info = self.format_furmark_info(furmark_info)

                # 在结果框中显示识别结果
                result_text = f"\n🔍 Furmark参数识别结果:\n"
                for param_name, value in formatted_info.items():
                    result_text += f"  {param_name}: {value}\n"

                self.root.after(0, self.update_result_text, result_text)

                # 保存识别结果到测试结果目录
                if self.test_result_subfolder:
                    self.save_furmark_info(formatted_info)

                # 依据温度与时间/FPS生成合格文本或弹窗
                try:
                    coreTemp = furmark_info.get('core_temperature', '').strip()
                    hotTemp = furmark_info.get('hotspot_temperature', '').strip()
                    fpsText = furmark_info.get('fps', '').strip()
                    frameRateText = furmark_info.get('frame_rate', '').strip()
                    timeText = furmark_info.get('stress_test_time', '').strip()

                    coreTempNum = self._extract_first_int(coreTemp)
                    hotTempNum = self._extract_first_int(hotTemp)
                    minutes, seconds = self._parse_time_to_min_sec(timeText)
                    fpsNumber = self._extract_first_int(fpsText)

                    if coreTempNum is not None and hotTempNum is not None and coreTempNum < 85 and hotTempNum < 95:
                        # 记录通过关键数值，供绘制报告一行阈值描述使用
                        self.furmark_core_temp_num = coreTempNum
                        self.furmark_hotspot_temp_num = hotTempNum
                        self.furmark_minutes = minutes
                        self.furmark_seconds = seconds
                        self.furmark_fps_num = fpsNumber
                        self.furmark_pass = True
                        # 直接缓存Furmark各字段，最终在绘图时重组文案
                        self.furmark_report_text = "OK"
                    else:
                        self.furmark_report_text = None
                        self.furmark_pass = False
                        self.root.after(0, lambda: messagebox.showwarning("提示", "Furmark测试不通过，请重新测试"))
                except Exception as _e:
                    print(f"生成Furmark报告文本失败: {_e}")
            else:
                self.root.after(0, self.update_result_text, "❌ Furmark参数识别失败\n")

        except Exception as e:
            print(f"处理Furmark OCR错误: {e}")
            self.root.after(0, self.update_result_text, f"❌ Furmark OCR处理错误: {e}\n")

    def process_3dmark_ocr(self, filename):
        """处理3DMark图片的OCR识别"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            # 构建完整的文件路径
            image_path = os.path.join(screenshot_date_dir, filename)

            if not os.path.exists(image_path):
                print(f"3DMark图片不存在: {image_path}")
                return

            # 进行OCR识别
            mark3d_info = self.process_3dmark_image(image_path)

            if mark3d_info:
                # 格式化识别结果
                formatted_info = self.format_3dmark_info(mark3d_info)

                # 在结果框中显示识别结果
                result_text = f"\n🔍 3DMark参数识别结果:\n"
                for param_name, value in formatted_info.items():
                    result_text += f"  {param_name}: {value}\n"

                self.root.after(0, self.update_result_text, result_text)

                # 保存识别结果到测试结果目录
                if self.test_result_subfolder:
                    self.save_3dmark_info(formatted_info)

                # 依据通过率生成合格文本
                try:
                    passText = mark3d_info.get('pass_rate', '').strip()
                    import re as _re
                    m = _re.search(r"(\d+(?:\.\d+)?)", passText)
                    passNumFloat = float(m.group(1)) if m else None
                    if passNumFloat is not None:
                        if passNumFloat >= 97:
                            self.mark3d_pass_num = passNumFloat
                            self.mark3d_pass_text = passText
                            self.mark3d_pass = True
                            self.mark3d_report_text = "OK"
                        else:
                            self.mark3d_report_text = None
                            self.mark3d_pass = False
                            self.mark3d_pass_text = passText
                            self.root.after(0, lambda: messagebox.showwarning("提示", "3DMark测试未达到97%，请重新测试"))
                except Exception as _e:
                    print(f"生成3DMark报告文本失败: {_e}")
            else:
                self.root.after(0, self.update_result_text, "❌ 3DMark参数识别失败\n")

        except Exception as e:
            print(f"处理3DMark OCR错误: {e}")
            self.root.after(0, self.update_result_text, f"❌ 3DMark OCR处理错误: {e}\n")

    def save_gpu_z_info(self, gpu_z_info, formatted_text=None, xianka_full_name=None):
        """保存GPU-Z识别结果到文件"""
        try:
            if not self.test_result_subfolder:
                return

            # 根据需求：不再生成参数txt文件，改为仅在界面显示/用于报告
            return

        except Exception as e:
            print(f"保存GPU-Z参数错误: {e}")

    @staticmethod
    def format_gpu_z_info(gpu_z_info):
        """格式化GPU-Z识别结果，使其更易读"""
        try:
            formatted_info = {}

            # 定义参数的中文名称映射
            param_names = {
                'gpu_name': 'GPU名称',
                'memory_size': '显存大小',
                'xianka_gys': '显卡供应商',
                'hexin_xinghao': '核心型号',
                'cuda_hexin': 'CUDA核心个数',
                'xiancun_type': '显存类型',
                'xaincun_size': '显存大小',
                'memery_frequency': '显存频率',
                'basic_core_frequency': '基础核心频率',
                'boost_core_frequency': '超频频率',
                'driver_version': '驱动版本',
                'bios_version': 'BIOS版本',
                'manufacturer': '制造商',
                'release_date': '发布日期'
            }

            for key, value in gpu_z_info.items():
                chinese_name = param_names.get(key, key)
                formatted_info[chinese_name] = value

            return formatted_info

        except Exception as e:
            print(f"格式化GPU-Z信息错误: {e}")
            return gpu_z_info

    @staticmethod
    def generate_xianka_full_name(gpu_z_info):
        """生成显卡全名变量"""
        try:
            # 获取显卡供应商和GPU名称
            supplier = gpu_z_info.get('xianka_gys', '').strip()
            gpu_name = gpu_z_info.get('gpu_name', '').strip()

            # 去除"NVIDIA GeForce"前缀
            if 'NVIDIA GeForce' in gpu_name:
                gpu_name = gpu_name.replace('NVIDIA GeForce', '').strip()

            # 计算显存大小（整数GB），用于显卡全名后缀
            memory_size_raw = gpu_z_info.get('xaincun_size', '').strip()
            memory_size_gb = TestTool.convert_memory_size_to_gb_integer(memory_size_raw)

            # 基础名称：供应商+GPU名字
            if supplier and gpu_name:
                base_name = f"{supplier}{gpu_name}"
            elif supplier:
                base_name = supplier
            elif gpu_name:
                base_name = gpu_name
            else:
                base_name = "未知显卡"

            # 如果显存大小可用，且GPU名称未包含该后缀，则追加为 "-XGB"
            if memory_size_gb and memory_size_gb != '未知':
                try:
                    import re as _re
                    if _re.search(rf"\b{_re.escape(memory_size_gb)}\s*$", base_name, _re.IGNORECASE):
                        return base_name
                    return f"{base_name}-{memory_size_gb}"
                except Exception:
                    return base_name
            return base_name

        except Exception as e:
            print(f"生成显卡全名错误: {e}")
            return "未知显卡"

    @staticmethod
    def convert_memory_size_to_gb(memory_size_str):
        """将显存大小转换为GB格式"""
        try:
            if not memory_size_str or memory_size_str == "裁剪失败":
                return "未知"

            # 提取数字
            import re
            numbers = re.findall(r'\d+', memory_size_str)
            if not numbers:
                return "未知"

            size_value = int(numbers[0])

            # 如果包含MB，转换为GB
            if 'MB' in memory_size_str.upper():
                return f"{size_value / 1024:.1f}GB"
            elif 'GB' in memory_size_str.upper():
                return f"{size_value}GB"
            else:
                # 假设是MB
                return f"{size_value / 1024:.1f}GB"

        except Exception as e:
            print(f"转换显存大小错误: {e}")
            return "未知"

    @staticmethod
    def convert_memory_size_to_gb_integer(memory_size_str):
        """将显存大小转换为整数GB格式"""
        try:
            if not memory_size_str or memory_size_str == "裁剪失败":
                return "未知"

            # 提取数字
            import re
            numbers = re.findall(r'\d+', memory_size_str)
            if not numbers:
                return "未知"

            size_value = int(numbers[0])

            # 如果包含MB，转换为GB（向上取整）
            if 'MB' in memory_size_str.upper():
                gb_value = size_value / 1024
                # 向上取整到最接近的整数
                if gb_value > int(gb_value):
                    gb_value = int(gb_value) + 1
                else:
                    gb_value = int(gb_value)
                return f"{gb_value}GB"
            elif 'GB' in memory_size_str.upper():
                return f"{size_value}GB"
            else:
                # 假设是MB，向上取整
                gb_value = size_value / 1024
                if gb_value > int(gb_value):
                    gb_value = int(gb_value) + 1
                else:
                    gb_value = int(gb_value)
                return f"{gb_value}GB"

        except Exception as e:
            print(f"转换显存大小为整数GB错误: {e}")
            return "未知"

    @staticmethod
    def ensure_gpu_name_suffix(gpu_name, memory_size_gb):
        """确保GPU名称以规范的显存容量后缀结尾（如 6GB/8GB/12GB）。
        - 若末尾已有 GB/G/B 或类似"6B"，替换为指定的 memory_size_gb
        - 若没有，则在末尾追加一个空格加 memory_size_gb
        """
        try:
            if not gpu_name:
                return gpu_name
            if not memory_size_gb or memory_size_gb == '未知':
                return gpu_name.strip()

            name = gpu_name.strip()

            # 已经是正确的后缀，直接返回
            import re as _re
            if _re.search(rf"\b{_re.escape(memory_size_gb)}\s*$", name, _re.IGNORECASE):
                return name

            # 捕获末尾像 "6GB"、"6G"、"6B" 的情况并替换为目标后缀
            pattern = _re.compile(r"(?:\s|-|_)?(\d{1,2})\s*(GB|G|B)\s*$", _re.IGNORECASE)
            if pattern.search(name):
                name = pattern.sub(f" {memory_size_gb}", name)
                return name.strip()

            # 否则直接追加
            return f"{name} {memory_size_gb}".strip()
        except Exception:
            return gpu_name.strip() if gpu_name else gpu_name

    @staticmethod
    def normalize_memory_type(xiancun_type):
        """规范化显存类型：
        - 去除多余空格
        - 将常见类型统一为：GDDR5/GDDR5X/GDDR6/GDDR6X/GDDR4/DDR5/DDR6 等
        - 若末尾误识别为字母 G（如 GDDRG），自动改成 6（GDDR6）
        - 若无法判断，保留清理后的内容
        """
        try:
            if not xiancun_type:
                return xiancun_type
            t = xiancun_type.upper().strip()

            # 常见映射和纠错
            replacements = {
                'GDDR5X': 'GDDR5X',
                'GDDR6X': 'GDDR6X',
                'GDDR5': 'GDDR5',
                'GDDR6': 'GDDR6',
                'GDDR4': 'GDDR4',
                'DDR6': 'DDR6',
                'DDR5': 'DDR5',
                'DDR4': 'DDR4',
            }

            # 精确命中
            if t in replacements:
                return replacements[t]

            # 子串命中优先级：6X > 6 > 5X > 5
            if 'GDDR6X' in t:
                return 'GDDR6X'
            if 'GDDR6' in t:
                return 'GDDR6'
            if 'GDDR5X' in t:
                return 'GDDR5X'
            if 'GDDR5' in t:
                return 'GDDR5'

            # 纠错：以 GDDR 开头，尾字母是 G 的，纠正为 GDDR6
            import re as _re
            if _re.match(r'^GDDR\w*G$', t):
                return 'GDDR6'

            # 其它 DDR 系列
            if 'DDR6' in t:
                return 'DDR6'
            if 'DDR5' in t:
                return 'DDR5'
            if 'DDR4' in t:
                return 'DDR4'

            return t
        except Exception:
            return xiancun_type

    @staticmethod
    def normalize_core_model(hexin_xinghao):
        """规范化GPU核心型号，处理OCR识别错误：
        - 常见错误：'6A1OG' -> 'GA106', '6A1O6' -> 'GA106'
        - 支持的核心型号模式：GA104, GA106, GA107, GA102, GA103, AD104, AD106, AD107等
        """
        try:
            if not hexin_xinghao or hexin_xinghao == '未知':
                return hexin_xinghao

            # 转换为大写并清理
            model = hexin_xinghao.upper().strip()

            # 如果已经是正确格式，直接返回
            if re.match(r'^G[AP]\d{3}$', model):
                return model

            # 处理常见的OCR识别错误
            corrections = {
                # 6A1OG -> GA106 (O被识别为0，G被识别为6)
                '6A1OG': 'GA106',
                '6A1O6': 'GA106',
                '6A106': 'GA106',
                '6A1O0': 'GA106',
                '6A100': 'GA106',

                # 6A1OG -> GA106 (其他变体)
                '6A1OG': 'GA106',
                '6A1O6': 'GA106',
                '6A106': 'GA106',

                # 6A104 -> GA104
                '6A104': 'GA104',
                '6A1O4': 'GA104',
                '6A104': 'GA104',

                # 6A102 -> GA102
                '6A102': 'GA102',
                '6A1O2': 'GA102',
                '6A102': 'GA102',

                # 6A107 -> GA107
                '6A107': 'GA107',
                '6A1O7': 'GA107',
                '6A107': 'GA107',

                # 6A103 -> GA103
                '6A103': 'GA103',
                '6A1O3': 'GA103',
                '6A103': 'GA103',

                # 6D104 -> AD104 (RTX 40系列)
                '6D104': 'AD104',
                '6D1O4': 'AD104',
                '6D104': 'AD104',

                # 6D106 -> AD106
                '6D106': 'AD106',
                '6D1O6': 'AD106',
                '6D106': 'AD106',

                # 6D107 -> AD107
                '6D107': 'AD107',
                '6D1O7': 'AD107',
                '6D107': 'AD107',
            }

            # 直接匹配
            if model in corrections:
                return corrections[model]

            # 模式匹配：6A1XX -> GA1XX, 6D1XX -> AD1XX
            import re as _re
            ga_match = _re.match(r'^6A1(\d{2})$', model)
            if ga_match:
                return f"GA1{ga_match.group(1)}"

            ad_match = _re.match(r'^6D1(\d{2})$', model)
            if ad_match:
                return f"AD1{ad_match.group(1)}"

            # 如果第一个字符是6，尝试替换为G
            if model.startswith('6'):
                corrected = 'G' + model[1:]
                # 验证格式是否正确
                if _re.match(r'^G[AP]\d{3}$', corrected):
                    return corrected

            return model

        except Exception as e:
            print(f"核心型号校正错误: {e}")
            return hexin_xinghao

    def generate_gpu_z_formatted_text(self, gpu_z_info):
        """生成GPU-Z特定格式的文本输出"""
        try:
            # 生成显卡全名
            xianka_full_name = self.generate_xianka_full_name(gpu_z_info)

            # 获取各个参数值
            gpu_name = gpu_z_info.get('gpu_name', '未知')
            hexin_xinghao = gpu_z_info.get('hexin_xinghao', '未知')
            cuda_hexin = gpu_z_info.get('cuda_hexin', '未知')
            xiancun_type = gpu_z_info.get('xiancun_type', '未知')
            xaincun_size = gpu_z_info.get('xaincun_size', '未知')
            basic_core_frequency = gpu_z_info.get('basic_core_frequency', '未知')
            boost_core_frequency = gpu_z_info.get('boost_core_frequency', '未知')
            memery_frequency = gpu_z_info.get('memery_frequency', '未知')

            # 修正核心型号：如果第一个字母是6，替换为G
            if hexin_xinghao and hexin_xinghao != '未知' and hexin_xinghao.startswith('6'):
                hexin_xinghao = 'G' + hexin_xinghao[1:]

            # 应用智能核心型号校正
            hexin_xinghao = self.normalize_core_model(hexin_xinghao)

            # 修正显存类型：去除'('及其后面的字符，并做尾字母校正
            if xiancun_type and xiancun_type != '未知':
                if '(' in xiancun_type:
                    xiancun_type = xiancun_type.split('(')[0].strip()
                xiancun_type = self.normalize_memory_type(xiancun_type)

            # 转换显存大小为GB格式，只保留整数
            memory_size_gb = self.convert_memory_size_to_gb_integer(xaincun_size)

            # 生成格式化的文本
            formatted_text = f"""显卡全名: {xianka_full_name}

1. 参数是否合格  是  
GPU-Z显示显卡的各个参数都符合({gpu_name})原厂标准:核心型号:({hexin_xinghao}),CUDA核心个数:({cuda_hexin}),显存类型:({xiancun_type})显存大小({memory_size_gb}),基础核心频率({basic_core_frequency}),Boost频率({boost_core_frequency}),显存频率({memery_frequency})

2. 是否维修过: 否
GPU-Z未显示核心数、显存容量等关键参数异常，子系统ID与原厂一致，无第三方维修痕迹（如电容更换、散热模块改造）"""

            return formatted_text, xianka_full_name

        except Exception as e:
            print(f"生成GPU-Z格式化文本错误: {e}")
            return "生成格式化文本失败", "未知显卡"

    def _build_gpu_paragraph(self):
        """构造GPU-Z段落文本，按用户给定格式。"""
        try:
            info = self.last_gpu_z_info or {}
            # 复用和清洗流程
            gpu_name = info.get('gpu_name', '未知')
            hexin_xinghao = info.get('hexin_xinghao', '未知')
            cuda_hexin = info.get('cuda_hexin', '未知')
            xiancun_type = info.get('xiancun_type', '未知')
            xaincun_size = info.get('xaincun_size', '未知')
            basic_core_frequency = info.get('basic_core_frequency', '未知')
            boost_core_frequency = info.get('boost_core_frequency', '未知')
            memery_frequency = info.get('memery_frequency', '未知')

            # 规则与清洗一致
            if hexin_xinghao and hexin_xinghao != '未知' and hexin_xinghao.startswith('6'):
                hexin_xinghao = 'G' + hexin_xinghao[1:]

            # 应用智能核心型号校正
            hexin_xinghao = self.normalize_core_model(hexin_xinghao)
            if xiancun_type and xiancun_type != '未知':
                if '(' in xiancun_type:
                    xiancun_type = xiancun_type.split('(')[0].strip()
                xiancun_type = self.normalize_memory_type(xiancun_type)
            memory_size_gb = self.convert_memory_size_to_gb_integer(xaincun_size)

            return (
                f"GPU-Z显示显卡的各个参数都符合({gpu_name})原厂标准:"
                f"核心型号:({hexin_xinghao}),CUDA核心个数:({cuda_hexin}),"
                f"显存类型:({xiancun_type})显存大小({memory_size_gb}),"
                f"基础核心频率({basic_core_frequency}),Boost频率({boost_core_frequency}),"
                f"显存频率({memery_frequency})"
            )
        except Exception:
            return ""

    def _build_furmark_paragraph(self):
        """构造 Furmark 段落文本，三条指定格式。"""
        try:
            if not self.furmark_pass:
                return ""
            coreT = self.furmark_core_temp_num if self.furmark_core_temp_num is not None else '—'
            hotT = self.furmark_hotspot_temp_num if self.furmark_hotspot_temp_num is not None else '—'
            minutes = self.furmark_minutes if self.furmark_minutes is not None else 0
            seconds = self.furmark_seconds if self.furmark_seconds is not None else 0
            fpsNum = self.furmark_fps_num if self.furmark_fps_num is not None else '—'

            line1 = f"1) 拷机是时间: ({minutes}分钟{seconds}秒,满足 ≥20 分钟要求)"
            line2 = f"2) 温度控制: 核心温度稳定在{coreT}℃(≤85℃安全阈值),热点温度{hotT}℃(≤95℃)"
            line3 = f"3) 稳定性: 帧率保持在 {fpsNum} FPS  无黑屏、绿屏或降频现象。"
            return "\n".join([line1, line2, line3])
        except Exception:
            return ""

    def _build_3dmark_paragraph(self):
        try:
            if not self.mark3d_pass or self.mark3d_pass_num is None:
                return ""
            passText = self.mark3d_pass_text if self.mark3d_pass_text else f"{self.mark3d_pass_num}%"
            return f"Time Spy压力测试循环20次帧率稳定性达{passText}（超过了97%通过标准），压力测试通过。"
        except Exception:
            return ""

    def _build_footer_paragraph(self):
        try:
            gpuFullName = self.xianka_full_name_cached or "未知显卡"
            current_time = datetime.now().strftime('%Y年%m月%d日 %H:%M')
            return (
                f"备注信息\n"
                f"显卡名称: {self.test_product_number}-{gpuFullName}\n"
                f"测试人: 林工\n"
                f"测试时间: {current_time}\n"
                f"测试机构: 恒信二手电脑配件专营店工程部"
            )
        except Exception:
            return ""

    def save_furmark_info(self, furmark_info):
        """保存Furmark识别结果到文件"""
        try:
            if not self.test_result_subfolder:
                return

            # 需求变更：不再输出 Furmark 参数txt
            return

        except Exception as e:
            print(f"保存Furmark参数错误: {e}")

    @staticmethod
    def format_furmark_info(furmark_info):
        """格式化Furmark识别结果，使其更易读"""
        try:
            formatted_info = {}

            # 定义参数的中文名称映射
            param_names = {
                'stress_test_time': '拷机时间',
                'fps': 'FPS',
                'frame_rate': '帧率',
                'core_temperature': '核心温度',
                'hotspot_temperature': '热点温度'
            }

            for key, value in furmark_info.items():
                chinese_name = param_names.get(key, key)
                formatted_info[chinese_name] = value

            return formatted_info

        except Exception as e:
            print(f"格式化Furmark信息错误: {e}")
            return furmark_info

    @staticmethod
    def _extract_first_int(text):
        """提取文本中的第一个整数，找不到返回None"""
        try:
            if not text:
                return None
            import re as _re
            m = _re.search(r"(\d+)", str(text))
            if not m:
                return None
            return int(m.group(1))
        except Exception:
            return None

    @staticmethod
    def _parse_time_to_min_sec(time_text):
        """解析 Furmark 时间文本，如 'Time 0:20:11' -> (20, 11)"""
        try:
            # 期待格式：Time H:MM:SS 或 MM:SS
            import re as _re
            text = str(time_text)
            # 先匹配 H:MM:SS
            m = _re.search(r"(\d+):(\d{1,2}):(\d{1,2})", text)
            if m:
                hours = int(m.group(1))
                minutes = int(m.group(2))
                seconds = int(m.group(3))
                total_minutes = hours * 60 + minutes
                return total_minutes, seconds
            # 匹配 MM:SS
            m2 = _re.search(r"(\d{1,2}):(\d{1,2})", text)
            if m2:
                minutes = int(m2.group(1))
                seconds = int(m2.group(2))
                return minutes, seconds
            return 0, 0
        except Exception:
            return 0, 0

    def save_3dmark_info(self, mark3d_info):
        """保存3DMark识别结果到文件"""
        try:
            if not self.test_result_subfolder:
                return

            # 需求变更：不再输出 3DMark 参数txt
            return

        except Exception as e:
            print(f"保存3DMark参数错误: {e}")

    @staticmethod
    def format_3dmark_info(mark3d_info):
        """格式化3DMark识别结果，使其更易读"""
        try:
            formatted_info = {}

            # 定义参数的中文名称映射
            param_names = {
                'pass_rate': '通过率'
            }

            for key, value in mark3d_info.items():
                chinese_name = param_names.get(key, key)
                formatted_info[chinese_name] = value

            return formatted_info

        except Exception as e:
            print(f"格式化3DMark信息错误: {e}")
            return mark3d_info

    def remove_duplicate_files(self, files):
        """删除重复的截图文件"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            # 保留第一个文件，删除其他文件
            for file in files[1:]:
                file_path = os.path.join(screenshot_date_dir, file)
                if os.path.exists(file_path):
                    os.remove(file_path)
                    print(f"删除重复文件: {file}")
        except Exception as e:
            print(f"删除重复文件错误: {e}")

    def remove_old_file(self, filename):
        """删除旧的截图文件"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            file_path = os.path.join(screenshot_date_dir, filename)
            if os.path.exists(file_path):
                os.remove(file_path)
                print(f"删除旧文件: {filename}")
        except Exception as e:
            print(f"删除旧文件错误: {e}")

    def monitor_screenshots(self):
        """监控截图目录的变化"""
        try:
            while self.is_testing:
                # 获取当前截图文件列表
                current_files = self.get_screenshot_files()

                # 找出新增的文件,这里每次只新增一个,不要新增太多个,否则容易出错呢
                new_files = current_files - self.screenshot_files_before
                if len(new_files) >= 2:
                    new_files = list(new_files)[:1]

                if new_files:
                    self.handle_new_screenshots(new_files)
                    # 更新文件列表
                    self.screenshot_files_before = self.screenshot_files_before.union(new_files)

                    # 检查是否四类图片就绪，若就绪则自动结束测试生成报告
                    try:
                        current_date = datetime.now().strftime('%Y-%m')
                        screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)
                        if os.path.exists(screenshot_date_dir) and self._check_all_tests_ready(screenshot_date_dir) and self.mark3d_finished and self.furmark_finished and self.gpu_z_finished and self.ludashi_finished:
                            self.root.after(500, self._auto_finish_when_ready)
                    except Exception:
                        pass

                # 每3秒检查一次
                time.sleep(3)

        except Exception as e:
            print(f"监控截图目录错误: {e}")

    @staticmethod
    def safe_imread(image_path):
        """安全读取图片，支持中文路径"""
        try:
            image = Image.open(image_path)
            # 转换为RGB模式（如果是RGBA，去掉透明通道）
            if image.mode in ('RGBA', 'LA', 'P'):
                image = image.convert('RGB')
            return image
        except Exception as e:
            print(f"读取图片失败: {image_path}, 错误: {e}")
            return None

    @staticmethod
    def resize_image_to_width(image, target_width):
        """将图像按宽度等比例缩放"""
        width, height = image.size
        ratio = target_width / width
        new_height = int(height * ratio)
        return image.resize((target_width, new_height), Image.Resampling.LANCZOS)

    @staticmethod
    def resize_image_to_height(image, target_height):
        """将图像按高度等比例缩放"""
        width, height = image.size
        ratio = target_height / height
        new_width = int(width * ratio)
        return image.resize((new_width, target_height), Image.Resampling.LANCZOS)

    @staticmethod
    def crop_bottom_quarter(image, extra_crop_pixels=0):
        """截掉图片下面1/4的部分，并可选择额外减少指定像素数"""
        width, height = image.size
        new_height = int(height * 0.9)
        new_height = max(1, new_height - extra_crop_pixels)
        return image.crop((0, 0, width, new_height))

    @staticmethod
    def horizontal_concat(img1, img2):
        """水平拼接两张图片"""
        width1, height1 = img1.size
        width2, height2 = img2.size

        # 创建新的图片，宽度为两张图片宽度之和，高度为较高的那张图片的高度
        new_width = width1 + width2
        new_height = max(height1, height2)

        # 创建新图片
        new_image = Image.new('RGB', (new_width, new_height), (255, 255, 255))

        # 粘贴第一张图片
        new_image.paste(img1, (0, 0))
        # 粘贴第二张图片
        new_image.paste(img2, (width1, 0))

        return new_image

    @staticmethod
    def vertical_concat(img1, img2):
        """垂直拼接两张图片"""
        width1, height1 = img1.size
        width2, height2 = img2.size

        # 创建新的图片，高度为两张图片高度之和，宽度为较宽的那张图片的宽度
        new_width = max(width1, width2)
        new_height = height1 + height2

        # 创建新图片
        new_image = Image.new('RGB', (new_width, new_height), (255, 255, 255))

        # 粘贴第一张图片
        new_image.paste(img1, (0, 0))
        # 粘贴第二张图片
        new_image.paste(img2, (0, height1))

        return new_image

    def joint_image(self, image_dict):
        """拼接图像"""
        image_3d = image_dict["3DMark"]
        image_furmark = image_dict["furmark"]
        image_gpu_z = image_dict["GPU-Z"]
        image_ludashi = image_dict["ludashi"]

        print(f"3d.size:{image_3d.size},furmark.size:{image_furmark.size},gpu_z.size:{image_gpu_z.size},ludashi.size:{image_ludashi.size}")

        # 1. 先裁剪3DMark和FurMark，截掉下面1/4
        image_3d_cropped = self.crop_bottom_quarter(image_3d, extra_crop_pixels=80)
        image_furmark_cropped = self.crop_bottom_quarter(image_furmark)

        print(f"裁剪后 - 3d.size:{image_3d_cropped.size}, furmark.size:{image_furmark_cropped.size}")

        # 2. 3DMark和FurMark垂直拼接
        target_width = image_3d_cropped.size[0]
        furmark_resized = self.resize_image_to_width(image_furmark_cropped, target_width)

        # 垂直拼接3DMark和FurMark
        top_section = self.vertical_concat(image_3d_cropped, furmark_resized)
        print(f"顶部拼接区域尺寸: {top_section.size}")

        # 3. 鲁大师和GPU-Z水平拼接
        target_height = image_ludashi.size[1]
        gpu_z_resized = self.resize_image_to_height(image_gpu_z, target_height)

        # 水平拼接鲁大师和GPU-Z
        bottom_section = self.horizontal_concat(image_ludashi, gpu_z_resized)
        print(f"底部拼接区域尺寸: {bottom_section.size}")

        # 4. 将两个拼接结果垂直拼接
        bottom_width = top_section.size[0]
        bottom_resized = self.resize_image_to_width(bottom_section, bottom_width)

        # 最终垂直拼接
        final_image = self.vertical_concat(top_section, bottom_resized)
        print(f"最终拼接图像尺寸: {final_image.size}")

        # 5. 整体缩放到原来的0.75倍
        width, height = final_image.size
        new_width = int(width * 0.75)
        new_height = int(height * 0.75)
        final_image_resized = final_image.resize((new_width, new_height), Image.Resampling.LANCZOS)
        print(f"缩放后最终图像尺寸: {final_image_resized.size}")

        return final_image_resized

    def build_report_image(self, target_width):
        """绘制与拼接图同宽的报告面板（自适应高度）。
        - 参数段落每行宽度从40增加到60字符。
        - Furmark段落为三行固定格式。
        - 3DMark段落保留小数。
        - 末尾追加备注信息。
        """
        try:
            # 组装三部分文本（不再包含备注信息，按用户最新要求仅绘制1~4项）
            gpuParagraph = self._build_gpu_paragraph()
            furmarkParagraph = self._build_furmark_paragraph()
            mark3dParagraph = self._build_3dmark_paragraph()

            if not any([gpuParagraph, furmarkParagraph, mark3dParagraph]):
                return None

            # 画布参数：宽度等于最终拼接图宽度；这里在调用处会与拼接图同宽拼接
            # 先用一个常规宽度，真正宽度会在拼接时通过 resize 处理
            panelWidth = int(target_width)
            panelHeight = 500
            padding = 20

            try:
                normalFont = ImageFont.truetype("msyh.ttc", 27)
                boldFont = ImageFont.truetype("msyhbd.ttc", 27)
            except Exception:
                normalFont = ImageFont.load_default()
                boldFont = ImageFont.load_default()

            img = Image.new('RGB', (panelWidth, panelHeight), (255, 255, 255))
            draw = ImageDraw.Draw(img)

            # 绘制函数，推荐在分隔符处换行（不强制硬切），避免把"MHz/℃/GB"等单位拆开
            def wrap_text(text):
                if not text:
                    return []
                max_len = 60
                break_chars = set([' ', ',', '，', '、', '。', ';', '；'])
                lines = []
                buf = ''
                for ch in text:
                    buf += ch
                    if len(buf) >= max_len and ch in break_chars:
                        lines.append(buf.rstrip())
                        buf = ''
                if buf:
                    lines.append(buf)
                return lines

            x = padding
            y = padding
            lineHeight = 35

            # 预计算行集合，便于动态高度
            gpuLines = wrap_text(gpuParagraph)
            repairLines = wrap_text("GPU-Z未显示核心数、显存容量等关键参数异常，子系统ID与原厂一致，无第三方维修痕迹（如电容更换、散热模块改造）")
            furmarkLines = furmarkParagraph.split("\n") if furmarkParagraph else []
            mark3dLines = wrap_text(mark3dParagraph)

            # 动态高度估算
            totalLines = 0
            # 四个标题
            totalLines += 4
            totalLines += len(gpuLines) + len(repairLines) + len(furmarkLines) + len(mark3dLines)
            # 空行数
            totalLines += 3

            # 备注信息
            gpuFullName = self.xianka_full_name_cached or "未知显卡"
            current_time = datetime.now().strftime('%Y年%m月%d日 %H:%M')
            productFullName = f"{self.test_product_number}-{gpuFullName}" if self.test_product_number else gpuFullName
            remarks = [
                f"- 显卡名称: {productFullName}",
                f"- 测试人: 林工",
                f"- 测试时间: {current_time}",
                f"- 测试机构: 恒信二手电脑配件专营店",
            ]
            totalLines += 1 + len(remarks)

            lineHeight = 35
            panelHeight = padding * 2 + totalLines * lineHeight + 10

            img = Image.new('RGB', (panelWidth, panelHeight), (255, 255, 255))
            draw = ImageDraw.Draw(img)

            # 1. 参数是否合格（加粗）
            draw.text((x, y), "1. 参数是否合格  是", fill=(0, 0, 0), font=boldFont)
            y += lineHeight
            for l in gpuLines:
                draw.text((x, y), l, fill=(0, 0, 0), font=normalFont)
                y += lineHeight

            # 空行
            y += 6

            # 2. 是否维修过（加粗）
            draw.text((x, y), "2. 是否维修过: 否", fill=(0, 0, 0), font=boldFont)
            y += lineHeight
            for l in repairLines:
                draw.text((x, y), l, fill=(0, 0, 0), font=normalFont)
                y += lineHeight

            # 空行
            y += 6

            # 3. Furmark是否通过（加粗）
            draw.text((x, y), "3. Furmark是否通过: 是", fill=(0, 0, 0), font=boldFont)
            y += lineHeight
            for l in furmarkLines:
                draw.text((x, y), l, fill=(0, 0, 0), font=normalFont)
                y += lineHeight

            # 空行
            y += 6

            # 4. 3DMark是否通过（加粗）
            draw.text((x, y), "4. 3DMark是否通过:是", fill=(0, 0, 0), font=boldFont)
            y += lineHeight
            for l in mark3dLines:
                draw.text((x, y), l, fill=(0, 0, 0), font=normalFont)
                y += lineHeight

            # 空行
            y += 6

            # 备注信息
            draw.text((x, y), "备注信息", fill=(0, 0, 0), font=boldFont)
            y += lineHeight
            for l in remarks:
                draw.text((x, y), l, fill=(0, 0, 0), font=normalFont)
                y += lineHeight

            return img
        except Exception as e:
            print(f"生成报告图片失败: {e}")
            return None

    @staticmethod
    def save_jointed_image(image, output_path):
        """保存拼接后的图像，支持中文路径"""
        try:
            image.save(output_path, 'JPEG', quality=95)
            print(f"拼接图像已保存到: {output_path}")
            return True
        except Exception as e:
            print(f"保存图片时发生错误: {output_path}, 错误: {e}")
            return False

    def process_test_images(self):
        """处理测试图片：拼接并保存到测试结果目录"""
        try:
            # 构建截图目录路径（截图目录 + 当前年月）
            current_date = datetime.now().strftime('%Y-%m')
            screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)

            if not os.path.exists(screenshot_date_dir):
                print(f"截图目录不存在: {screenshot_date_dir}")
                return False

            # 获取所有图片文件
            image_dict = {}
            for file in os.listdir(screenshot_date_dir):
                if file.endswith('.jpg') or file.endswith('.png'):
                    image_path = os.path.join(screenshot_date_dir, file)
                    image = self.safe_imread(image_path)

                    if image is None:
                        print(f"跳过无法读取的图片: {image_path}")
                        continue

                    # 根据文件名识别测试类型
                    if "3dmark" in file.lower():
                        image_dict["3DMark"] = image
                    elif "furmark" in file.lower():
                        image_dict["furmark"] = image
                    elif "gpu-z" in file.lower():
                        image_dict["GPU-Z"] = image
                    elif "computerz_cn" in file.lower() or "鲁大师" in file:
                        image_dict["ludashi"] = image

            # 检查是否成功读取了所有需要的图片
            required_images = ["3DMark", "furmark", "GPU-Z", "ludashi"]
            missing_images = [img for img in required_images if img not in image_dict]

            if missing_images:
                print(f"警告: 缺少以下图片: {missing_images}")
                return False

            # 拼接图像
            jointed_image = self.joint_image(image_dict)

            # 生成报告图片（如有必要，宽度与拼接图一致，高度固定500）
            report_image = self.build_report_image(jointed_image.size[0])
            if report_image is not None:
                # 报告图 + 原拼接图 垂直拼接
                final_with_report = self.vertical_concat(report_image, jointed_image)
            else:
                final_with_report = jointed_image

            # 保存前：根据"货号-显卡全名"重命名文件夹并确定文件名
            gpuFullName = self.xianka_full_name_cached or "未知显卡"
            baseName_raw = f"{self.test_product_number}-{gpuFullName}" if self.test_product_number else gpuFullName
            baseName = self._sanitize_filename(baseName_raw)

            # 若需要则重命名文件夹（若同名存在则清理其中图片后复用）
            try:
                desired_dir = os.path.normpath(os.path.join(self.result_dir, baseName))
                if os.path.normpath(self.test_result_subfolder) != desired_dir:
                    if os.path.exists(desired_dir) and os.path.isdir(desired_dir):
                        for f in os.listdir(desired_dir):
                            if f.lower().endswith(('.jpg', '.png')):
                                try:
                                    os.remove(os.path.join(desired_dir, f))
                                except Exception:
                                    pass
                    os.rename(self.test_result_subfolder, desired_dir)
                    self.test_result_subfolder = desired_dir
            except Exception as _e:
                print(f"重命名结果文件夹失败，将沿用原目录: {_e}")

            # 保存拼接后的图像到测试结果目录（文件名：原名称-测试报告.jpg）
            output_filename = f"{baseName}-测试报告.jpg"
            output_path = os.path.join(self.test_result_subfolder, output_filename)

            if not self.save_jointed_image(final_with_report, output_path):
                return False

            # 记录最新报告路径，供后续"查看报告"使用
            self.latest_report_path = output_path

            # 复制原图到测试结果目录
            for test_type, image in image_dict.items():
                # 根据测试类型确定文件名
                if test_type == "3DMark":
                    filename = "3DMark.jpg"
                elif test_type == "furmark":
                    filename = "furmark.jpg"
                elif test_type == "GPU-Z":
                    filename = "GPU-Z.jpg"
                elif test_type == "ludashi":
                    filename = "鲁大师.jpg"
                else:
                    filename = "未知图像.jpg"
                # 保存原图
                original_path = os.path.join(self.test_result_subfolder, filename)
                if not self.save_jointed_image(image, original_path):
                    print(f"保存原图失败: {filename}")

            # 删除截图目录中的图片
            for file in os.listdir(screenshot_date_dir):
                if file.endswith('.jpg') or file.endswith('.png'):
                    file_path = os.path.join(screenshot_date_dir, file)
                    try:
                        os.remove(file_path)
                        print(f"删除截图文件: {file}")
                    except Exception as e:
                        print(f"删除文件失败: {file}, 错误: {e}")

            return True

        except Exception as e:
            print(f"处理测试图片时发生错误: {e}")
            return False

    def init_ui(self):
        """初始化用户界面"""
        # 创建自定义按钮样式
        style = ttk.Style()
        style.configure('Large.TButton', font=('黑体', 12))  # 按钮使用黑体

        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.grid(row=0, column=0, sticky="nsew")

        # 配置网格权重 - 左右各占一半
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(0, weight=1)  # 文字区域
        main_frame.columnconfigure(1, weight=1)  # 按钮区域
        main_frame.rowconfigure(0, weight=1)

        # 左侧文字区域框架
        left_frame = ttk.Frame(main_frame)
        left_frame.grid(row=0, column=0, sticky="nsew", padx="0 10")

        # 标题标签
        title_label = ttk.Label(left_frame, text="显卡测试流程", font=("微软雅黑", 16, "bold"))
        title_label.pack(anchor=tk.W, pady="0 5")

        # 测试流程提示文字
        instruction_text = """1) 安装新显卡,正常开机
2) 打开测试工具
3) 点击开始测试按钮
4) 单项测试并截图
5) 检测截图结果,最后只保留4张正确图
6) 点击结束测试按钮,测试完成"""

        instruction_label = ttk.Label(
            left_frame,
            text=instruction_text,
            font=("微软雅黑", 16),
            foreground="orange",
            justify=tk.LEFT,
            wraplength=400
        )
        instruction_label.pack(anchor=tk.W, pady="0 10")

        # 测试结果显示区域
        result_frame = ttk.Frame(left_frame)
        result_frame.pack(fill=tk.BOTH, expand=True, pady="10 0")

        # 结果显示标题
        result_title = ttk.Label(result_frame, text="测试结果", font=("微软雅黑", 12, "bold"))
        result_title.pack(anchor=tk.W, pady="0 5")

        # 结果显示文本框
        self.result_text = scrolledtext.ScrolledText(
            result_frame,
            height=15,
            width=45,
            wrap=tk.WORD,
            font=("微软雅黑", 10)
        )
        self.result_text.pack(fill=tk.BOTH, expand=True)

        # 右侧按钮区域框架
        right_frame = ttk.Frame(main_frame)
        right_frame.grid(row=0, column=1, sticky="nsew")

        # 按钮标题
        button_title = ttk.Label(right_frame, text="控制面板", font=("微软雅黑", 12, "bold"))
        button_title.pack(pady="0 10")

        # 目录设置区域
        dir_frame = ttk.LabelFrame(right_frame, text="目录设置", padding="5")
        dir_frame.pack(fill=tk.BOTH, expand=True, pady="0 10")

        # 目录显示区域 - 放在最上面
        display_frame = ttk.Frame(dir_frame)
        display_frame.pack(fill=tk.X, pady="5 10")

        # 截图目录显示标签
        self.screenshot_label = ttk.Label(
            display_frame,
            text="截图目录: 未设置",
            font=("微软雅黑", 9),
            anchor=tk.W,
            justify=tk.LEFT
        )
        self.screenshot_label.pack(fill=tk.X, pady="0 5", padx="5")

        # 测试结果目录显示标签
        self.result_label = ttk.Label(
            display_frame,
            text="测试结果目录: 未设置",
            font=("微软雅黑", 9),
            anchor=tk.W,
            justify=tk.LEFT
        )
        self.result_label.pack(fill=tk.X, pady="0 5", padx="5")

        # 按钮区域 - 第一行：设置按钮
        button_row1 = ttk.Frame(dir_frame)
        button_row1.pack(fill=tk.X, pady="5 5")

        # 截图目录设置按钮
        self.screenshot_button = ttk.Button(
            button_row1,
            text="截图目录设置",
            command=lambda: self.select_directory('screenshot_dir', '选择截图保存目录'),
            style="Large.TButton",
            width=20
        )
        self.screenshot_button.pack(side=tk.LEFT, padx="5 2")

        # 测试结果目录设置按钮
        self.result_dir_button = ttk.Button(
            button_row1,
            text="测试结果目录设置",
            command=lambda: self.select_directory('result_dir', '选择测试结果保存目录'),
            style="Large.TButton",
            width=20
        )
        self.result_dir_button.pack(side=tk.RIGHT, padx="2 5")

        # 打开目录区域
        open_dir_frame = ttk.LabelFrame(right_frame, text="打开目录", padding="5")
        open_dir_frame.pack(fill=tk.X, pady="10 10")

        # 打开目录按钮区域
        open_button_row = ttk.Frame(open_dir_frame)
        open_button_row.pack(fill=tk.X, pady="5 5")

        # 打开截图目录按钮
        self.open_screenshot_button = ttk.Button(
            open_button_row,
            text="打开截图目录",
            command=self.open_screenshot_directory,
            style="Large.TButton",
            width=20
        )
        self.open_screenshot_button.pack(side=tk.LEFT, padx="5 2")

        # 打开测试结果目录按钮
        self.open_result_button = ttk.Button(
            open_button_row,
            text="打开测试结果目录",
            command=self.open_result_directory,
            style="Large.TButton",
            width=20
        )
        self.open_result_button.pack(side=tk.RIGHT, padx="2 5")

        # 测试控制区域
        test_frame = ttk.LabelFrame(right_frame, text="测试控制", padding="5")
        test_frame.pack(fill=tk.BOTH, expand=True, pady="0 0")

        # 开始测试按钮
        self.start_button = ttk.Button(
            test_frame,
            text="开始测试",
            command=self.start_test,
            style="Large.TButton",
            width=20
        )
        self.start_button.pack(pady="5 10")

        # 结束测试按钮
        self.stop_button = ttk.Button(
            test_frame,
            text="结束测试",
            command=self.stop_test,
            state=tk.DISABLED,
            style="Large.TButton",
            width=20
        )
        self.stop_button.pack(pady="0 10")

        # 初始化目录显示
        self.update_directory_display()

    def start_test(self):
        """开始测试"""
        if not self.is_testing:
            # 检查截图目录是否设置
            if not self.screenshot_dir:
                messagebox.showwarning("警告", "请先设置截图目录")
                return

            # 创建测试结果子文件夹
            if not self.create_test_result_subfolder():
                return

            # 初始化测试状态
            self.is_testing = True
            self.start_button.config(state=tk.DISABLED)
            self.stop_button.config(state=tk.NORMAL)

            # 清空之前的测试结果
            self.test_results = {}
            self.duplicate_detection = {}

            # 记录测试开始前的截图文件
            self.screenshot_files_before = self.get_screenshot_files()

            # 记录测试开始时间
            self.test_start_time = datetime.now()

            # 在结果框中显示开始信息
            self.result_text.insert(tk.END, f"[{self.test_start_time.strftime('%Y-%m-%d %H:%M:%S')}] 测试开始...\n")
            self.result_text.insert(tk.END, f"当前测试商品货号: {self.test_product_number}\n")
            self.result_text.insert(tk.END, f"请将测试结果保存到: {self.test_result_subfolder}\n")
            self.result_text.insert(tk.END, "请使用ALT+X快捷键进行截图测试...\n")
            self.result_text.see(tk.END)

            # 启动截图监控线程
            self.test_thread = threading.Thread(target=self.monitor_screenshots)
            self.test_thread.daemon = True
            self.test_thread.start()

            # 启动后立即检查是否已具备四类图片，若具备则自动结束
            try:
                current_date = datetime.now().strftime('%Y-%m')
                screenshot_date_dir = os.path.join(self.screenshot_dir, current_date)
                if os.path.exists(screenshot_date_dir) and self._check_all_tests_ready(screenshot_date_dir):
                    self.root.after(500, self._auto_finish_when_ready)
            except Exception:
                pass

    def stop_test(self):
        """结束测试"""
        if self.is_testing:
            self.is_testing = False
            self.start_button.config(state=tk.NORMAL)
            self.stop_button.config(state=tk.DISABLED)
            self.mark3d_finished = False
            self.furmark_finished = False
            self.gpu_z_finished = False
            self.ludashi_finished = False

            # 记录测试结束时间
            self.test_end_time = datetime.now()

            # 计算测试耗时（分钟）
            test_duration_minutes = (self.test_end_time - self.test_start_time).total_seconds() / 60

            # 在现有内容下面添加结束信息（不清空之前的内容）
            self.result_text.insert(tk.END, f"\n[{self.test_end_time.strftime('%Y-%m-%d %H:%M:%S')}] 测试完成，耗时: {test_duration_minutes:.1f}分钟\n")
            self.result_text.insert(tk.END, "货号: ")

            # 标记货号样式的起止位置，仅标记编号本身
            startIndex = self.result_text.index(tk.END)
            self.result_text.insert(tk.END, self.test_product_number)
            endIndex = self.result_text.index(tk.END)

            # 先插入一个换行，避免后续文本继承标签样式
            self.result_text.insert(tk.END, "\n")

            # 再给编号区间打标签（不包含换行）
            self.result_text.tag_add("product_number", startIndex, endIndex)
            self.result_text.tag_config("product_number", foreground="red", font=("微软雅黑", 20, "bold"))

            self.result_text.see(tk.END)

            # 处理测试图片：拼接并保存（静默处理，不显示信息）
            if self.test_results:
                self.process_test_images()

                # 生成后弹窗提示，并支持查看报告
                def open_report():
                    try:
                        if self.latest_report_path and os.path.exists(self.latest_report_path):
                            if os.name == 'nt':
                                os.startfile(self.latest_report_path)
                            else:
                                os.system(f'xdg-open "{self.latest_report_path}"')
                    except Exception:
                        pass
                try:
                    top = tk.Toplevel(self.root)
                    top.title("测试完成")
                    top.geometry("500x300")  # 设置弹窗大小
                    top.resizable(False, False)  # 禁止调整大小
                    
                    # 居中显示弹窗（相对于主窗口）
                    top.update_idletasks()
                    root_x = self.root.winfo_x()
                    root_y = self.root.winfo_y()
                    root_width = self.root.winfo_width()
                    root_height = self.root.winfo_height()
                    
                    x = root_x + (root_width // 2) - (500 // 2)
                    y = root_y + (root_height // 2) - (300 // 2)
                    top.geometry(f"500x300+{x}+{y}")
                    
                    # 设置弹窗内容
                    main_frame = ttk.Frame(top, padding="30")
                    main_frame.pack(fill=tk.BOTH, expand=True)
                    
                    # 标题
                    title_label = ttk.Label(main_frame, text="🎉 测试报告已生成", font=("微软雅黑", 16, "bold"))
                    title_label.pack(pady=(0, 30))
                    
                    # 说明文字
                    desc_label = ttk.Label(main_frame, text="测试已完成，报告已保存到测试结果目录", 
                                         font=("微软雅黑", 12), foreground="gray")
                    desc_label.pack(pady=(0, 40))
                    
                    # 按钮
                    button_frame = ttk.Frame(main_frame)
                    button_frame.pack()
                    
                    view_button = ttk.Button(button_frame, text="查看报告", 
                                           command=lambda: [open_report(), top.destroy()], 
                                           style="Large.TButton", width=18)
                    view_button.pack(side=tk.LEFT, padx=(0, 15))
                    
                    close_button = ttk.Button(button_frame, text="关闭", 
                                            command=top.destroy, width=18)
                    close_button.pack(side=tk.LEFT)
                    
                    # 设置弹窗为模态
                    top.transient(self.root)
                    top.grab_set()
                    top.focus_set()
                except Exception:
                    messagebox.showinfo("提示", "测试报告已生成")

    def update_result_text(self, text):
        """更新结果文本框（在主线程中调用）"""
        self.result_text.insert(tk.END, text)
        self.result_text.see(tk.END)

    def run(self):
        """运行应用程序"""
        # 程序启动后自动开始测试
        try:
            self.root.after(200, self.start_test)
        except Exception:
            pass
        self.root.mainloop()

    def _check_all_tests_ready(self, screenshot_date_dir):
        """检查截图目录是否包含四类目标图片（3DMark, Furmark, GPU-Z, 鲁大师）"""
        try:
            kinds = {"3dmark": False, "furmark": False, "gpu-z": False, "ludashi": False}
            for f in os.listdir(screenshot_date_dir):
                fl = f.lower()
                if fl.endswith(('.jpg', '.png')):
                    if "3dmark" in fl:
                        kinds["3dmark"] = True
                    elif "furmark" in fl:
                        kinds["furmark"] = True
                    elif "gpu-z" in fl:
                        kinds["gpu-z"] = True
                    elif "computerz_cn" in fl or "鲁大师" in f:
                        kinds["ludashi"] = True
            return all(kinds.values())
        except Exception:
            return False

    def _auto_finish_when_ready(self):
        """当四类图片都准备好时，自动点击结束测试。"""
        try:
            if self.is_testing:
                self.stop_test()
        except Exception:
            pass

    @staticmethod
    def _sanitize_filename(name):
        """清理Windows非法文件名字符。"""
        try:
            import re as _re
            return _re.sub(r'[\\/:*?"<>|]', '_', str(name))
        except Exception:
            return name

    def __del__(self):
        """析构函数，关闭数据库连接"""
        if self.db_connection:
            self.db_connection.close()


def main():
    """主函数"""
    app = TestTool()
    app.run()


if __name__ == '__main__':
    main()
